package Myrandom;

import java.util.Random;

/**
 * Copyright (C), 2020-2020, XXX有限公司
 * FileName: Myrandom
 * Author:   cakin
 * Date:     2020/1/6
 * Description: 随机数相关函数
 */
public class Myrandom {
    private static Random random;    // pseudo-random number generator
    private static long seed;

    static {
        // this is how the seed was set in Java 1.4
        seed = System.currentTimeMillis();
        random = new Random(seed);
    }

    private static void validateNotNull(Object x) {
        if (x == null) {
            throw new IllegalArgumentException("argument is null");
        }
    }

    public static double uniform() {
        return random.nextDouble();
    }

    // 随机返回[a,b)之间的一个double值
    public static double uniform( double a, double b ) {
        if (!(a < b)) {
            throw new IllegalArgumentException("invalid range: [" + a + ", " + b + ")");
        }
        return a + uniform() * (b - a);
    }

    // 随机返回[0..N)之间的一个int值
    public static int uniform(int n) {
        if (n <= 0) throw new IllegalArgumentException("argument must be positive: " + n);
        return random.nextInt(n);
    }

    // 返回[a,b)之间一个int值
    public static int uniform(int a, int b) {
        if ((b <= a) || ((long) b - a >= Integer.MAX_VALUE)) {
            throw new IllegalArgumentException("invalid range: [" + a + ", " + b + ")");
        }
        return a + uniform(b - a);
    }

    // 随机将double数组的元素排序
    public static void shuffle(double[] a) {
        validateNotNull(a);
        int n = a.length;
        for (int i = 0; i < n; i++) {
            int r = i + uniform(n-i);     // between i and n-1
            double temp = a[i];
            a[i] = a[r];
            a[r] = temp;
        }
    }

    // 概率分布函数
    public static int discrete(double[] probabilities) {
        if (probabilities == null) throw new IllegalArgumentException("argument array is null");
        double EPSILON = 1.0E-14;
        double sum = 0.0;
        for (int i = 0; i < probabilities.length; i++) {
            if (!(probabilities[i] >= 0.0))
                throw new IllegalArgumentException("array entry " + i + " must be nonnegative: " + probabilities[i]);
            sum += probabilities[i];
        }
        if (sum > 1.0 + EPSILON || sum < 1.0 - EPSILON)
            throw new IllegalArgumentException("sum of array entries does not approximately equal 1.0: " + sum);

        //参考 https://www.jianshu.com/p/d51f8267e6b9
        while (true) {
            double r = uniform();
            sum = 0.0;
            for (int i = 0; i < probabilities.length; i++) {
                sum = sum + probabilities[i];
                if (sum > r) return i;
            }
        }
    }

    // 伯努利分布
    public static boolean bernoulli(double p) {
        if (!(p >= 0.0 && p <= 1.0))
            throw new IllegalArgumentException("probability p must be between 0.0 and 1.0: " + p);
        return uniform() < p;
    }

    // 正态分布，期望值为0，标准差为1
    // https://baijiahao.baidu.com/s?id=1630253104547104560&wfr=spider&for=pc
    public static double gaussian() {
        // use the polar form of the Box-Muller transform
        double r, x, y;
        do {
            x = uniform(-1.0, 1.0);
            y = uniform(-1.0, 1.0);
            r = x*x + y*y;
        } while (r >= 1 || r == 0);
        return x * Math.sqrt(-2 * Math.log(r) / r);

        // Remark:  y * Math.sqrt(-2 * Math.log(r) / r)
        // is an independent random gaussian
    }

    // 正态分布，期望值为mu，标准差为sigma
    public static double gaussian(double mu, double sigma) {
        return mu + sigma * gaussian();
    }

    public static void main( String[] args ) {
        System.out.println("uniform():"+uniform());
        System.out.println("uniform( double 1.0, double 4.0 ):"+uniform(1.0,4.0));
        System.out.println("uniform(int 5):"+uniform(5));
        System.out.println("uniform(int 1, int 7):"+uniform(1,7));
        double[] a = {1.8,3.4,5.6,4.3,8.7,2.3};
        shuffle(a);
        for(double m:a){
            System.out.print(m+" ");
        }
        System.out.println();

        double[] probabilities={0.3,0.3,0.4};

        System.out.println("discrete:"+discrete(probabilities));
        System.out.println("bernoulli():"+bernoulli(0.6));
        System.out.println("gaussian():"+gaussian());
        System.out.println("gaussian(double mu, double sigma):"+gaussian(0,1));
    }
}
